Skip to content

feat(provider): add Passbolt provider support#1030

Open
VDuchauffour wants to merge 1 commit intohelmfile:mainfrom
VDuchauffour:feat/passbolt
Open

feat(provider): add Passbolt provider support#1030
VDuchauffour wants to merge 1 commit intohelmfile:mainfrom
VDuchauffour:feat/passbolt

Conversation

@VDuchauffour
Copy link

@VDuchauffour VDuchauffour commented Feb 27, 2026

Summary

  • Add a new passbolt provider to retrieve secrets from Passbolt (https://www.passbolt.com/), an open-source team password manager, using the official go-passbolt (https://github.com/passbolt/go-passbolt) SDK
  • Support standard fields (password, username, name, uri, description) and custom fields (custom_fields/FieldName) via encrypted metadata (Passbolt v5.3+)
  • Works with both Passbolt CE and PRO editions

Usage

ref+passbolt://RESOURCE_UUID#/password
ref+passbolt://RESOURCE_UUID#/username
ref+passbolt://RESOURCE_UUID#/custom_fields/ProjectID
ref+passbolt://RESOURCE_UUID?address=https://passbolt.example.com#/password

Configuration

Supports both environment variables and URI query parameters:

Env Variable URI Param Description
PASSBOLT_BASE_URL address Passbolt server URL
PASSBOLT_GPG_KEY_FILE gpg_key_file Path to GPG private key file
PASSBOLT_GPG_KEY gpg_key GPG private key content (alternative to file)
PASSBOLT_GPG_PASSPHRASE passphrase GPG private key passphrase

Changes

  • pkg/providers/passbolt/passbolt.go — Provider implementation with GetString and GetStringMap support, thread-safe client initialization via sync.Once
  • pkg/providers/passbolt/passbolt_test.go — Unit tests covering config resolution, key parsing, field extraction, custom fields, and error cases (451 lines)
  • vals.go, pkg/stringprovider/stringprovider.go — Register passbolt provider in the provider registry
  • README.md — Documentation with usage examples and configuration reference
  • go.mod / go.sum — Add github.com/passbolt/go-passbolt dependency

Notes

  • MFA is not supported — a service account without MFA should be used for automation
  • Custom fields require Passbolt v5.3+ with encrypted metadata enabled
  • When no #/field fragment is specified, GetStringMap returns all available fields as a map

@yxxhero
Copy link
Member

yxxhero commented Feb 27, 2026

@VDuchauffour please fix DCO issue.

@VDuchauffour
Copy link
Author

@yxxhero it's done :)

@yxxhero
Copy link
Member

yxxhero commented Feb 27, 2026

@VDuchauffour oh. please rebase your code. a lot of commits.

@VDuchauffour
Copy link
Author

@yxxhero yep, sorry. It's done

@yxxhero
Copy link
Member

yxxhero commented Feb 27, 2026

@VDuchauffour ci issue.

Signed-off-by: Vincent Duchauffour <vincent.duchauffour@proton.me>
@VDuchauffour
Copy link
Author

@yxxhero fixed

@yxxhero yxxhero requested a review from Copilot February 28, 2026 00:00
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new passbolt backend to vals for retrieving secrets (including Passbolt v5.3+ custom fields), wires it into provider registries, and documents usage/config.

Changes:

  • Introduce pkg/providers/passbolt provider (GetString / GetStringMap) with sync.Once client initialization
  • Register passbolt provider in runtime and stringprovider registries
  • Add Passbolt docs and new Go module dependencies + unit tests

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
vals.go Registers the new passbolt provider in the runtime provider switch.
pkg/stringprovider/stringprovider.go Adds passbolt to the string provider factory.
pkg/providers/passbolt/passbolt.go Implements Passbolt provider logic (config resolution, login, field/custom-field extraction).
pkg/providers/passbolt/passbolt_test.go Adds unit tests for config resolution and custom-field helpers/structures.
README.md Documents Passbolt provider usage and configuration.
go.mod Adds github.com/passbolt/go-passbolt dependency (+ indirect deps).
go.sum Records checksums for newly added dependencies.

Comment on lines +205 to +213
func (p *provider) GetString(key string) (string, error) {
if err := p.ensureClient(); err != nil {
return "", err
}

parts := strings.Split(key, "/")
if len(parts) == 0 || parts[0] == "" {
return "", fmt.Errorf("passbolt: key cannot be empty")
}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetString/GetStringMap validate the key after ensureClient(). This makes invalid keys return unrelated client-init errors (e.g., missing GPG key) instead of "key cannot be empty", and it also prevents the key-parsing tests from exercising the parsing error paths. Parse/validate the key first (including extracting uuid / field), then call ensureClient() only once the key is known-valid, so callers get deterministic and accurate errors for malformed references.

Copilot uses AI. Check for mistakes.
Comment on lines +253 to +261
func (p *provider) GetStringMap(key string) (map[string]interface{}, error) {
if err := p.ensureClient(); err != nil {
return nil, err
}

parts := strings.Split(key, "/")
if len(parts) == 0 || parts[0] == "" {
return nil, fmt.Errorf("passbolt: key cannot be empty")
}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetString/GetStringMap validate the key after ensureClient(). This makes invalid keys return unrelated client-init errors (e.g., missing GPG key) instead of "key cannot be empty", and it also prevents the key-parsing tests from exercising the parsing error paths. Parse/validate the key first (including extracting uuid / field), then call ensureClient() only once the key is known-valid, so callers get deterministic and accurate errors for malformed references.

Copilot uses AI. Check for mistakes.
Comment on lines +118 to +132
func (p *provider) getCustomFields(ctx context.Context, resourceID string) []customField {
resource, err := p.client.GetResource(ctx, resourceID)
if err != nil || resource.Metadata == "" {
return nil
}

rType, err := p.client.GetResourceType(ctx, resource.ResourceTypeID)
if err != nil {
return nil
}

rawMeta, err := helper.GetResourceMetadata(ctx, p.client, resource, rType)
if err != nil {
return nil
}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getCustomFields silently drops all errors by returning nil for any failure (resource fetch/type fetch/metadata decrypt/secret decrypt/unmarshal). This can lead to incorrect behavior where GetString(".../custom_fields/X") reports "not found" even though the provider actually failed to read/decrypt custom-field data. Consider returning (fields []customField, err error) (or at least logging the underlying error) and propagating it up through getResource/GetString/GetStringMap so failures are distinguishable from "no custom fields present".

Copilot uses AI. Check for mistakes.
Comment on lines +143 to +155
if err := json.Unmarshal([]byte(rawMeta), &metaRaw); err != nil || len(metaRaw.CustomFields) == 0 {
return nil
}

secret, err := p.client.GetSecret(ctx, resource.ID)
if err != nil {
return nil
}

rawSecret, err := p.client.DecryptSecretWithResourceID(resource.ID, secret.Data)
if err != nil {
return nil
}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getCustomFields silently drops all errors by returning nil for any failure (resource fetch/type fetch/metadata decrypt/secret decrypt/unmarshal). This can lead to incorrect behavior where GetString(".../custom_fields/X") reports "not found" even though the provider actually failed to read/decrypt custom-field data. Consider returning (fields []customField, err error) (or at least logging the underlying error) and propagating it up through getResource/GetString/GetStringMap so failures are distinguishable from "no custom fields present".

Copilot uses AI. Check for mistakes.
Comment on lines +164 to +166
if err := json.Unmarshal([]byte(rawSecret), &secretData); err != nil {
return nil
}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getCustomFields silently drops all errors by returning nil for any failure (resource fetch/type fetch/metadata decrypt/secret decrypt/unmarshal). This can lead to incorrect behavior where GetString(".../custom_fields/X") reports "not found" even though the provider actually failed to read/decrypt custom-field data. Consider returning (fields []customField, err error) (or at least logging the underlying error) and propagating it up through getResource/GetString/GetStringMap so failures are distinguishable from "no custom fields present".

Copilot uses AI. Check for mistakes.
Comment on lines +278 to +283
if err == nil {
t.Error("Expected error for empty key")
}

// The error will be from ensureClient or GetStringMap
// We just need to verify an error is returned
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test asserts err == nil twice, which is redundant and makes the intent harder to read. Remove one of the checks (and keep a single assertion with a single message) to simplify the test.

Suggested change
if err == nil {
t.Error("Expected error for empty key")
}
// The error will be from ensureClient or GetStringMap
// We just need to verify an error is returned
// The error will be from ensureClient or GetStringMap; we just need to verify an error is returned.

Copilot uses AI. Check for mistakes.
Comment on lines +1235 to +1239
Parameters (override environment variables):

- `address`: Passbolt server URL
- `gpg_key_file`: Path to GPG private key file
- `passphrase`: GPG private key passphrase
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The provider supports gpg_key as a configuration input (and it’s documented earlier in the PR description/env var list), but the README “Parameters” section omits the gpg_key query parameter. Add gpg_key here (and ideally also include it in the bracketed URI example) so users know they can pass key content directly via URI params.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants